#include <string.h>
#include <stdio.h>
#include <stdlib.h>


extern "C"{
#include "lua.h"
#include "lualib.h"
#include "lauxlib.h"
};
#include "zvty.h"

#define ZVTY_MT "ZVTY_MT"

class luavtystyb : public ZVTYStubI
{
public:
	luavtystyb(lua_State*,int);
	int callbackRef;
	lua_State* L;
	ZVTY m_vty;
	int str_free(char* errstr){if (errstr) free(errstr); return 0;}
	int vty_outbuf_reset();
	int vty_auth(const char* name,const char* pwd,char** errmsg);
	int vty_write(const char *buf, size_t nbytes);
	int vty_prompt();
	int vty_execute();
	int vty_complete_command();
	int vty_hello();
};
int luavtystyb::vty_complete_command()
{
	if (callbackRef==-1)return -1;

	vty_write("\n",1);

	lua_rawgeti(L, LUA_REGISTRYINDEX, callbackRef);
	lua_pushstring(L,"complete_command");
	lua_gettable(L,-2);
	if (lua_isfunction(L,-1))
	{
		lua_call(L,0,1);
	}
	else
	{
		lua_pop(L,2);
		return 0;
	}

	if (lua_isstring(L,-1))
	{
		const char* s = lua_tostring(L,-1);
		if (s!=NULL&&s[0]!='\0')
		{
			vty_prompt();
			m_vty.redraw_line();
			m_vty.insert_word_overwrite(0,s+m_vty.cp);
			lua_pop(L,2);
			return 0;
		}
	}
	lua_pop(L,2);
	vty_prompt();
	m_vty.redraw_line();
	return 0;
}
int luavtystyb::vty_auth(const char* name,const char* pwd,char** errmsg)
{
	if (callbackRef==-1)return -1;
	lua_rawgeti(L, LUA_REGISTRYINDEX, callbackRef);
	lua_pushstring(L,"auth");
	lua_gettable(L,-2);
	if (lua_isfunction(L,-1))
	{
		lua_pushstring(L,name);
		lua_pushstring(L,pwd);
		lua_call(L,2,2);
		if (errmsg)
		{
			const char* s=lua_tostring(L,-1);
			if (s)
				*errmsg=strdup(s);
		}
		int r=lua_tointeger(L,-2);
		lua_pop(L,3);
		return r;
	}
	else
	{
		lua_pop(L,2);
		return -1;
	}
}
int luavtystyb::vty_write(const char *buf, size_t nbytes)
{
	if (callbackRef==-1)return -1;
	lua_rawgeti(L, LUA_REGISTRYINDEX, callbackRef);
	lua_pushstring(L,"write");
	lua_gettable(L,-2);
	if (lua_isfunction(L,-1))
	{
		lua_pushlstring(L,buf,nbytes);
		lua_call(L,1,0);
		lua_pop(L,1);
	}
	else
	{
		lua_pop(L,2);
	}
	return 0;
}

#define ZVTYFUN_DEF(k) int luavtystyb::vty_##k()\
{\
	if (callbackRef==-1)return -1;\
	lua_rawgeti(L, LUA_REGISTRYINDEX, callbackRef);\
	lua_pushstring(L,#k);\
	lua_gettable(L,-2);\
	if (lua_isfunction(L,-1))\
	{\
		lua_call(L,0,0);\
		lua_pop(L,1);\
	}\
	else\
	{\
		lua_pop(L,2);\
	}\
	return 0;\
}
ZVTYFUN_DEF(outbuf_reset)
ZVTYFUN_DEF(prompt)
ZVTYFUN_DEF(execute)
ZVTYFUN_DEF(hello)

luavtystyb::luavtystyb(lua_State* s,int ro)
{
	this->L=s;
	callbackRef=ro;
	m_vty.init(this);
}

int static zvtysetuser(lua_State* L)
{
	luavtystyb** pstub=(luavtystyb**)luaL_checkudata(L, 1, ZVTY_MT);
	luavtystyb*stub=*pstub;
	if (stub->callbackRef!=-1)
		luaL_unref(L, LUA_REGISTRYINDEX, stub->callbackRef);
	stub->callbackRef=-1;
	lua_pushvalue(L, 2);
	stub->callbackRef = luaL_ref(L, LUA_REGISTRYINDEX);
	stub->L = L;
	return 0;
}
int static zvtyread(lua_State* L)
{
	luavtystyb** pstub=(luavtystyb**)luaL_checkudata(L, 1, ZVTY_MT);
	luavtystyb*stub=*pstub;
	size_t len=0;
	const char* buf=lua_tolstring(L,2,&len);
	stub->m_vty.read((unsigned char*)buf,len);
	if (stub->m_vty.status==ZVTY::VTY_CLOSE)
		lua_pushnil(L);
	else
		lua_pushinteger(L,0);
	return 1;
}
int static zvtysetstat(lua_State* L)
{
	luavtystyb** pstub=(luavtystyb**)luaL_checkudata(L, 1, ZVTY_MT);
	luavtystyb*stub=*pstub;
	if (lua_isnumber(L,2))
		*(int*)(&stub->m_vty.status)=lua_tointeger(L,2);
	lua_pushinteger(L,stub->m_vty.status);
	return 1;
}
int static zvtygetbuf(lua_State* L)
{
	luavtystyb** pstub=(luavtystyb**)luaL_checkudata(L, 1, ZVTY_MT);
	luavtystyb*stub=*pstub;
	lua_pushstring(L,stub->m_vty.buf);
	return 1;
}
int zvty_newvty(lua_State* L)
{
	int refobj=-1;
	if(lua_istable(L,1))
	{
		lua_pushvalue(L, 1);
		refobj = luaL_ref(L, LUA_REGISTRYINDEX);
	}
	luavtystyb **base = (luavtystyb**)lua_newuserdata(L, sizeof(luavtystyb*));
	*base=new luavtystyb(L,refobj);
	luaL_getmetatable(L, ZVTY_MT);
	lua_setmetatable(L, -2);
	return 1;
}


static int zvty_gc(lua_State* L)
{
	luavtystyb** pstub=(luavtystyb**)luaL_checkudata(L, 1, ZVTY_MT);
	luavtystyb* stub=*pstub;
	if(stub) {
		if (stub->callbackRef!=-1)
			luaL_unref(L, LUA_REGISTRYINDEX, stub->callbackRef);
		stub->callbackRef=-1;
		delete stub;
	}
	return 0;
}

static luaL_Reg base_funcs[] = {
	{ "setuser", zvtysetuser },
	{"setstat",zvtysetstat},
	{"onread",zvtyread},
	{"getbuf",zvtygetbuf},
	{ NULL, NULL }
};

static luaL_Reg funcs[] = {
	{ "new", zvty_newvty },
	{ NULL, NULL }
};


typedef struct {
	const char* name;
	int value;
} namedInteger;


static namedInteger consts[] = {
	{"NAME_NODE", 0},
	{"AUTH_NODE", 1},
	{"XXX_NODE", 2},
	{"NORMAL",0},
	{"CLOSE",1},
	{"MORE",2},
	{"MORELINE",3},
	{NULL, 0}
};

void setNamedIntegers(lua_State* L, namedInteger* p) {
	while(p->name) {
		lua_pushinteger(L, p->value);
		lua_setfield(L, -2, p->name);
		p++;
	}
}
extern "C"{

#ifdef _WIN32
 __declspec(dllexport) int luaopen_zvty(lua_State* L)
#else
int luaopen_zvty(lua_State* L)
#endif
{
	lua_settop(L, 0);
	/* Setup metatable */
	luaL_newmetatable(L, ZVTY_MT);
	lua_newtable(L);
	luaL_register(L, NULL, base_funcs);
	lua_setfield(L, -2, "__index");
	lua_pushcfunction(L, zvty_gc);
	lua_setfield(L, -2, "__gc");
	lua_pop(L, 1);

	luaL_register(L, "zvty", funcs);
	setNamedIntegers(L, consts);
	return 1;
}

};
